/**
 * 
 */
package Utils.connections;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import main.SharedPref;

import Utils.Concurrent.AsyncTask;
import Utils.Concurrent.TaskRunError;

/**
 * @author Yuval create connections and Statements that the applications uses
 *         during run time
 * 
 *         each thread task that need to connect to the server, ask for a
 *         statement, and he can do the work, after he finished, the task return
 *         the satatment to the connection/statement pool
 */
public class ConnectionPool {
	private Connection[] connections;
	private List<Connection> freeConnections;
	// for every connection we make a Statement so we can handle killing the
	// thread
	private Map<Connection, Statement> connectionToStatements;
	private Map<AsyncTask<Object>, Connection> tasksToConnection;

	public ConnectionPool() throws TaskRunError {
		super();
		prepareAll();
	}

	/*
	 * try to close all the connections, this function is called only when
	 * closing the application
	 */
	public void closeConnections() {
		for (Connection con : connections) {
			if (con != null)
				try {
					connectionToStatements.get(con).cancel();
					connectionToStatements.get(con).close();
					con.close();
				} catch (SQLException e) {
					e.printStackTrace();

				}
		}

	}

	/*
	 * try to stop the query from executing, and also return the
	 * statement\connection to the pool
	 */
	public synchronized void stopDBoperation(AsyncTask<Object> task) {
		Statement stm = connectionToStatements.get(tasksToConnection.get(task));
		// System.out.println(freeConnections.size());
		try {
			if (!stm.isClosed()) {
				stm.cancel();
			}
		} catch (SQLException e) {
			e.printStackTrace();
			// throw new TaskRunError("Connection Error");
		}
		Connection conn = tasksToConnection.remove(task);
		freeConnections.add(conn);
		synchronized (freeConnections) {
			freeConnections.notify();
		}
	}

	/*
	 * return the connection after using it
	 */
	public synchronized void returnConnection(AsyncTask<Object> task) {
		Connection conn = tasksToConnection.remove(task);
		freeConnections.add(conn);
		synchronized (freeConnections) {
			freeConnections.notify();
		}
	}

	/*
	 * get a connection, only the insert or update\delete operation uses this
	 * function, other task should use the getStaement function
	 */
	public synchronized Connection getConnection(AsyncTask<Object> task)
			throws InterruptedException {
		Connection conn = null;
		if (freeConnections.isEmpty()) {
			synchronized (freeConnections) {
				freeConnections.wait();
			}
		}

		conn = freeConnections.get(0);
		freeConnections.remove(conn);
		tasksToConnection.put(task, conn);

		return conn;
	}

	/*
	 * if a task need a statement to execute some queries, he ask for a
	 * statement from the pool
	 */
	public synchronized Statement getStatement(AsyncTask<Object> task)
			throws InterruptedException {
		Connection conn = null;
		if (freeConnections.isEmpty()) {
			synchronized (freeConnections) {
				freeConnections.wait();
			}

		}
		conn = freeConnections.get(0);
		freeConnections.remove(conn);
		tasksToConnection.put(task, conn);

		return connectionToStatements.get(conn);
	}

	private void prepareAll() throws TaskRunError {
		connections = new Connection[20];
		freeConnections = new ArrayList<Connection>();
		connectionToStatements = new HashMap<Connection, Statement>();
		tasksToConnection = new HashMap<AsyncTask<Object>, Connection>();
		openConnections();
	}

	/**
	 * 
	 * create all the connection and the statements
	 * 
	 * @throws TaskRunError
	 *             @
	 */
	public void openConnections() throws TaskRunError {
		// loading the driver
		try {
			Class.forName(SharedPref.forName);
		} catch (ClassNotFoundException e) {
			System.out.println("Unable to load the MySQL JDBC driver..");
			throw new TaskRunError("Connection Error");
		}
		System.out.println("Driver loaded successfully");

		// creating the connection
		System.out.print("Trying to connect.. ");
		try {
			for (int i = 0; i < connections.length; i++) {
				connections[i] = DriverManager.getConnection(SharedPref.DBurl,
						SharedPref.dbName, SharedPref.password);
				connectionToStatements.put(connections[i],
						connections[i].createStatement());
				connectionToStatements.get(connections[i]).setPoolable(true);
				connectionToStatements.get(connections[i]).setQueryTimeout(25);
				freeConnections.add(connections[i]);

			}
		} catch (SQLException e) {
			System.out.println("Unable to connect - " + e.toString());

			throw new TaskRunError("Connection Error");
		}

		System.out.println("Connected!");

	}

}
